Skip to content

feat(search): add OpenAI search passthrough#175

Merged
Menci merged 45 commits into
mainfrom
worktree-agent-a8a2e1aa14b68090a
Jul 16, 2026
Merged

feat(search): add OpenAI search passthrough#175
Menci merged 45 commits into
mainfrom
worktree-agent-a8a2e1aa14b68090a

Conversation

@Menci

@Menci Menci commented Jul 11, 2026

Copy link
Copy Markdown
Owner

Summary

Add a default-off OpenAI search passthrough that lets Responses hosted web search and Codex standalone web.run use a selected Codex or Custom upstream with /alpha/search support. The existing Tavily, Microsoft Grounding, or Jina configuration remains the default search backend, Anthropic Messages is unaffected, the selected upstream must be visible to the calling API key, and passthrough never falls back to another search provider.

Persist the toggle, upstream, and model in search settings, expose the dependent upstream/model controls in the dashboard, and carry the required shape through strict import/export format v10. The provider contract gains callAlphaSearch: Codex forwards to the ChatGPT subscription endpoint, Custom uses its configurable OpenAI-compatible path, and unsupported provider kinds reject the operation.

Serve the general data-plane aliases at /alpha/search and /v1/alpha/search; the special /azure-api.codex/alpha/search path remains owned and mounted only by the Codex namespace. Shared alpha execution, upstream selection, Responses adaptation, and relay behavior live under tools/web-search/alpha-search, so the hosted web-search shim depends only on the reusable web-search implementation rather than on HTTP routes.

Local mode parses and executes supported alpha commands through the general search backend. Passthrough mode relays the upstream status, decoded body stream, content type, and representation-safe metadata while removing stale content coding/length, hop-by-hop headers, and upstream cookies. Raw alpha JSON remains intact. Responses preserves the alpha endpoint's model-facing output; because alpha results are opaque UI metadata and do not use the hosted-search snippet shape, the adapter emits an explicitly synthetic result only when the Responses surface needs result-shaped data.

Account affinity for a future pool of Codex accounts remains a TODO. This change intentionally adds neither fallback nor a loop-prevention protocol.

Evidence

The live-wire study covered 349 ChatGPT Codex /alpha/search requests and six Responses hosted-search comparisons. The redacted specification, probe runners, and per-case structural reports are attached in this PR comment.

Test plan

  • pnpm run test — 358 files / 4,359 tests
  • pnpm run lint
  • pnpm run typecheck — 19 workspace projects
  • Probe Azure's authenticated model control and candidate alpha-search paths — model control returned 200 and all six alpha-search candidates returned 404
  • Verify raw alpha search through the general backend, a static official alpha upstream, and a two-Floway cascade — HTTP 200 with decoded JSON and no stale content encoding/length
  • Run Codex CLI 0.144.4 with six isolated HOME/CODEX_HOME pairs across hosted/client tools × general/official/cascade routes — all six cells completed one web search and returned the expected IANA URL

Menci added 8 commits July 12, 2026 04:31
The Codex CLI's web-search tool POSTs a SearchRequest to `/alpha/search`
and feeds the returned `output` string back to the model. Serve it from
Floway's own configured web-search provider instead of proxying
chatgpt.com.

codex's `commands` is an object keyed by command kind
(`{search_query:[{q}], open:[{ref_id}], find:[{ref_id,pattern}], …}`) —
byte-for-byte the shape the Responses web_search shim already parses. So
extract the protocol-neutral execution engine (command parsing, provider
resolution, search/open/find execution, page cache, IR building, text
rendering, domain filtering) out of the Responses shim into a shared
tools/web-search/operations.ts, and build both surfaces on it: the shim
wraps each op into a web_search_call wire item; the Codex endpoint
concatenates the rendered text of every op into one output string.

Unimplemented command kinds (image_query, click, screenshot, finance,
weather, sports, time, response_length) surface as deterministic per-op
error text. Disabled/missing search config surfaces the same in-band
'provider not configured' text the shim uses. Request validated with a
zod schema mirroring codex-rs SearchRequest; usage accounted per API key.
Route tests through the codex namespace: auth, zod schema rejection
(non-object commands, bad search_context_size), search/open/find command
execution and rendered output, per-key usage accounting, unsupported
command text, domain-filter blocking, empty commands, and disabled /
missing-credential provider states surfaced as in-band output.
Codex derives its remote web-search URL from different base URLs across
auth and provider modes. Mount the same authenticated, schema-validated
handler at /alpha/search and /v1/alpha/search in addition to the generated
config's /azure-api.codex/alpha/search path.
Parameterize routing, missing/invalid auth, and schema rejection across the
namespaced, root, and /v1 alpha-search paths so aliases cannot drift from
the primary handler.
Route /alpha/search to the gateway in Vite development, Cloudflare Static
Assets, and the Docker nginx topology. /v1/alpha/search is already covered
by each topology's existing /v1 prefix rule.
@Menci

Menci commented Jul 11, 2026

Copy link
Copy Markdown
Owner Author

Claude Code agent update: root compatibility aliases are being added. Codex joins alpha/search directly onto the configured model-provider base_url, so manual configs may call /alpha/search, /v1/alpha/search, or the recommended /azure-api.codex/alpha/search. The namespaced URL remains the generated default because its Azure marker preserves the fuller Codex behavior. Worker-first and Vite proxy routing will be updated for the unversioned root path as well.

Menci added 3 commits July 12, 2026 05:16
Codex recognizes a custom provider as eligible for its remote web.run tool
when the provider config carries a non-empty x-openai-actor-authorization
header. Treat that dashboard-generated marker as gateway-owned identity and
strip it before provider dispatch so it reaches Floway's routing decision but
never leaks to an LLM upstream.
Codex gates the remote web-search extension on either an OpenAI provider
identity or a non-empty x-openai-actor-authorization provider header. Keep
the provider key/name as floway/Floway so the wider is_openai behavior stays
disabled, and add a non-secret marker header that enables only the actor-auth
compatibility gates. The gateway scrubs the marker before upstream dispatch.

Cover the generated TOML so the provider identity, namespaced base URL, and
compatibility marker cannot drift.
@Menci Menci changed the title feat(codex): serve /alpha/search through Floway web search feat(codex): serve alpha search through Floway web search Jul 11, 2026
Menci added 2 commits July 12, 2026 05:23
The actor-authorization marker that enables remote web.run also makes Codex
consider its remote image-generation extension. Floway does not serve the
provider-relative images/generations path, so explicitly disable that feature
while retaining the marker for web search. Cover and document the generated
feature block.
Keep the inbound-header scrub list alphabetic, describe alpha-search output
in the parser's canonical operation order rather than JSON key order, and
make the shared truncated-page hint neutral across Responses and Codex
callers.
@Menci

Menci commented Jul 12, 2026

Copy link
Copy Markdown
Owner Author

Claude Code agent correction: the generated x-openai-actor-authorization marker, its special scrub rule, and image_generation = false are being removed. They were an unjustified attempt to influence Codex client gating, and the accompanying statement that Floway lacks image-generation endpoints was false: Floway serves both Images Generations and Images Edits on the standard root and /v1 paths. PR #175 will remain scoped to implementing /alpha/search through Floway's existing web-search service and exposing its route aliases; it will not fabricate authorization headers or disable unrelated client capabilities.

Menci added 14 commits July 12, 2026 14:03
Keep the generated Codex provider config faithful to Floway: do not inject
x-openai-actor-authorization, do not special-case that vendor header in the
gateway scrubber, and do not disable image generation. Remove the associated
gating claims and retain only the actual alpha-search route behavior in the
documentation.
…aa14b68090a

# Conflicts:
#	packages/gateway/src/data-plane/chat/responses/interceptors/server-tools/web-search.ts
#	packages/gateway/src/data-plane/codex/routes.ts
Add callAlphaSearch to the provider contract. Codex dispatches to the ChatGPT subscription endpoint with its OAuth account, custom dispatches to /v1/alpha/search with static auth, and other provider kinds reject the unsupported call.
Persist a default-off OpenAI search passthrough selection with upstream and model. Add the Settings toggle and scoped Codex/Custom upstream and chat-model pickers while preserving the existing general search provider as the default.
Resolve the configured upstream and model without fallback. Raw Codex alpha-search requests relay the selected provider response, while the existing Responses web-search shim adapts commands and structured results through the same provider call.
Thread the default-off passthrough shape through existing search fixtures and finish the merged web-search filter and Azure unsupported-provider paths.
Verify custom and Codex provider wires, raw alpha-route relay, Responses shim adaptation, persisted configuration, and the Codex/Custom-only settings picker.
Type the settings component against its minimal upstream picker shape, handle the nullable model store at the page boundary, and apply workspace lint rules to the alpha dispatcher path.
Keep search configuration export and import fixtures aligned with the OpenAI search passthrough settings.
Publish the required OpenAI search passthrough settings under transfer format v10 so incompatible payload shapes never share a version number.
Keep Codex request identity and optional metadata coherent, document the ChatGPT search path, and route Custom alpha search through the existing endpoint override policy.
Menci added 3 commits July 15, 2026 19:04
Make search configuration writes typed, remove lossy post-validation normalization, enforce the stored boolean domain, and keep the dashboard selector invariants explicit.
Use a callable alpha dispatcher, remove test-only compatibility exports, preserve cancellation through local search execution, and align comments and references with both local and passthrough modes.
@Menci Menci changed the title feat(codex): serve alpha search through Floway web search feat(search): add OpenAI search passthrough Jul 15, 2026
Menci added 4 commits July 15, 2026 19:32
Keep the README focused on public Floway endpoints and omit the internal Codex compatibility namespace.
Rebuild passthrough responses with mutable headers before returning through Hono so Custom-to-Floway alpha-search chains preserve the upstream stream, status, and headers on Node.
State that the selected upstream must support alpha search and that Anthropic Messages remains on the general search provider.
@Menci

Menci commented Jul 16, 2026

Copy link
Copy Markdown
Owner Author

Codex agent update: the ChatGPT Codex standalone-search wire probe is complete. This comment embeds the full publishable experiment archive; it is self-contained and does not depend on a gist or external host.

Evidence: 349 /alpha/search calls (156×200, 191×400, 2×401), six Responses hosted-search comparisons, and OpenAI Codex source pinned at 9ff47868eb2afeec579183e01bb9d3d3e9df2bcd. The archive contains the 1,000-line empirical spec, every probe/relay/sanitizer script, and redacted per-case reports.

Important findings for this PR:

  • Live alpha structured results had 907 entries and zero snippet fields. Requiring hosted-search {type,url,title,snippet} drops every genuine alpha result; output is the canonical model-facing payload and alpha results must remain opaque.
  • Alpha refs are scoped by top-level id, survive many calls, and work across model changes within the same ID. They require no replay of encrypted_output.
  • Outer validation is strict, but many semantic failures are HTTP 200 in-band diagnostics. A transparent passthrough must preserve status/body instead of converting them to gateway failures.
  • The live backend accepts allowed_callers: ["direct","programmatic"]; current Codex source still declares direct/shell/code_interpreter and sends only direct.
  • Current Codex normalizes model-emitted web.run arguments before HTTP, while raw clients can reach a broader validation surface. Current hosted search does not request or preserve hosted results/sources; main-branch alpha now does preserve opaque results out of band.

Redaction removed all access/refresh/ID tokens, account and user identifiers, emails, personal paths, proxy configuration, IPs, opaque encrypted payloads, account-plan headers, and full fetched page bodies. Successful responses retain types, key sets, counts, lengths, and diagnostic classifications. The supplied credential's refresh token was never read or invoked by the online runners.

floway-alpha-search-public-20260716-v2.zip

Menci added 2 commits July 16, 2026 20:29
Fetch exposes a decoded body while retaining upstream content-encoding and content-length. Rebuilding the alpha response with those stale headers makes a downstream Floway or Codex client decode plain JSON again and fail with a terminated stream.\n\nStrip hop-by-hop, stale representation-framing, and upstream cookie headers while retaining content type and vendor metadata. Cover the shared fetched-response relay behavior directly.
Live alpha-search results are opaque UI metadata and omit the hosted web_search snippet field. The existing compatibility filter therefore dropped every real result before silently falling back to the rendered output.\n\nKeep the upstream output as the lossless model-facing payload and emit one clearly synthetic Responses result when that surface needs result-shaped data. Raw alpha routes continue relaying the upstream response untouched.
@Menci

Menci commented Jul 16, 2026

Copy link
Copy Markdown
Owner Author

Codex agent update: the isolated Codex 2×3 end-to-end matrix is complete on the final PR head.

Codex 0.144.4 tool mode General search, passthrough off Static official alpha upstream Floway A → Floway B → official alpha
Responses hosted web_search Pass Pass Pass
Client web.run + /alpha/search Pass Pass Pass

Each cell used its own HOME and CODEX_HOME, emitted exactly one started/completed web_search item, had no turn.failed, and ended with the canonical IANA URL plus MATRIX_OK. The deterministic model observed the expected mutually exclusive tool shapes: hosted mode arrived as Floway's plain web_search shim function, while client mode arrived as namespaced web.run. Search output was non-empty and contained IANA in all six cells.

Routing evidence:

  • General-search cells recorded Microsoft Grounding search usage and made no alpha-upstream selection.
  • Official cells reached /backend-api/codex/alpha/search with HTTP 200.
  • Both cascade cells recorded /v1/responses → /v1/alpha/search → /v1/responses between A and B, followed by B's HTTP-200 official alpha call.
  • The official upstream was a Custom provider containing only the already-issued bearer access token. Its stored config had no refresh_token/refreshToken, and the isolated Codex homes contained only local Floway API keys.

The first cascade attempt exposed a real response-relay bug: Node Fetch had decoded upstream gzip bytes but Floway relayed the stale content-encoding: gzip, so the next Floway decoded plain JSON again and failed with terminated. Commit c60117be2 rebuilds fetched responses without stale framing/coding, hop-by-hop headers, or upstream cookies. The live regression probe now returns plain JSON with no content encoding/length, and direct plus cascaded client-tool cells pass.

The live alpha study also showed that official structured results omit hosted-search snippet in every observed entry. Commit 96a72d401 removes the unreachable "compatible structured results" assumption: raw alpha JSON remains untouched, while the Responses adapter preserves output as the lossless model payload and labels its single result-shaped projection as synthetic.

Final verification:

  • pnpm run test — 358 files, 4,361 tests passed
  • pnpm run lint — passed
  • pnpm run typecheck — 19 workspace projects passed
  • PR worktree clean and both commits pushed

Menci added 9 commits July 16, 2026 21:01
Move the alpha route, dispatcher, Responses adapter, response relay, and their tests into one web-search/alpha-search slice. The Codex route module now only mounts the shared handler, and the fetched-response helper no longer implies gateway-wide reuse.
Give the alpha-search data-plane surface ownership of its paths, validation, and handler while keeping provider execution, upstream dispatch, response adaptation, and relay behavior under tools/web-search/alpha-search. Codex routes now contain only the compatibility namespace they actually mount.
Let the general alpha-search data plane own only the root and /v1 aliases. Codex alone declares its special provider-relative path and reuses a path-agnostic route registrar. Hosted search remains coupled only to the tools/web-search implementation layer.
@Menci
Menci merged commit 38e0dc8 into main Jul 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant